// Created on 13 oct. 2006
package com.cofluent.web.server;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Helper for serializing/deserializing XML files to java classes.<BR>
 * In my opinion, this class has those advantages over xml mapping frameworks such as Castor:<BR>
 * - It is obfuscation insensitive (since it does not use xml mapping files, Java code is obfuscated at the same time),<BR>
 * - No extra XML configuration file for the mapping (no risk that the resource is not found, it is checked at compilation time),<BR>
 * - It uses the JDK to read XML, no additional jars needed.<BR>
 * <BR>
 * Example of a basic mapping to a testing class:<BR>
 * A class corresponding to the model:<BR> 
<verbatim>
class Item extends XMLSerializerUtil
{
	public Item(Element element) // required constructor for deserialization 
	{
		super(element);
	}
}

public class Test extends XMLSerializerUtil
{
	private static final String NAME_FIELD = "name"; // the name attribute
	private static final String ITEMS_FIELD = "items"; // the items attribute 

	public Test(Element element) // required constructor for deserialization 
	{
		super(element);
	}

	public String getName() // Get the attribute named "name" (mostly used for deserialization)
	{
		return getField (NAME_FIELD) ; // Get it directly from the DOM element, no cache is used
	}

	public void setName(String name) // Set the attribute named "name" (mostly used for serialization)
	{
		setField (NAME_FIELD, name) ;
	}

	public Vector getElements() // Get the inner items tags "<items></items>" ('s' indicate there can be many elements) (mostly used for deserialization) 
	{
		return getFields (Item.class, ITEMS_FIELD); // no cache
	}

	public Tool createAndAddItem () // Create and add an item (mostly used for serialization)
	{
		return (Tool) createAndAdd (Item.class, ITEMS_FIELD) ;
	}
	
	// Sample code to read the xml file
	public static void main(String [] args)
	{
	
	} 
}
</verbatim>
 * Now an sample of xml file could be:<BR>
<verbatim>
<xml ...>
<test name="sample">
	<items/>
	<items/>
</test>
</verbatim>
 * @author Christophe MOINE
 */
public class XMLSerializerUtil
{
//	private static org.apache.log4j.Logger logger=util.ConfiguredLogger.getLogger(XMLSerializerUtil.class);

	protected Element element ;

    public XMLSerializerUtil(Element element)
	{
    	this.element = element ;
	}

    public String getField (String fieldName) {
    	return element.getAttribute(fieldName) ;
    }
    
    protected void setField (String fieldName, String value)
    {
    	element.setAttribute(fieldName, value) ;
    }

    protected Vector getFields (Class clazz, String fieldName) {
    	Vector result = new Vector () ;
    	NodeList list = element.getElementsByTagName(fieldName);
    	for (int i = 0 ; i < list.getLength() ; i++)
    	{
    		Node node = list.item(i) ;
    		if (node instanceof Element)
    		{
    			result.add (createInstance(clazz, (Element) node)) ;
    		}
    	}
    	return result ;
    }
    
	protected Object createAndAdd(Class clazz, String fieldName)
	{
		Element newElement = element.getOwnerDocument().createElement(fieldName);
		// Add in the XML
		element.appendChild (newElement) ;
		// Add in the duplicate copy of the list 
		Object obj = createInstance (clazz, newElement) ;
		return obj;
	}
	
	protected void remove(Element element)
	{
		this.element.removeChild(element);
	}

	/**
     * Parses an XML file and returns a DOM document.
     * If validating is true, the contents is validated against the DTD
     * specified in the file.
     */ 
    public static Document parseXmlFile(InputStream stream, boolean validating) {
        try {
            // Create a builder factory
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(validating);

            // Create the builder and parse the file
            Document doc = factory.newDocumentBuilder().parse(stream);
            return doc;
        } catch (SAXException e) {
            // A parsing error occurred; the xml input is not valid
        	e.printStackTrace();
//        	logger.error ("Parsing xml error", e) ;
        } catch (ParserConfigurationException e) {
        	e.printStackTrace();
//        	logger.error ("Parser configuration error", e) ;
        } catch (IOException e) {
        	e.printStackTrace();
//        	logger.error ("Error while rading xml stream", e) ; ;
        }
        return null;
    }
    
    public static Object unmarshal(Class className, InputStream is)
    {
    	Object result = null ;
    	Document doc = parseXmlFile(is, false) ;
    	result = createInstance(className, doc.getDocumentElement());
    	return result ;
    }    

    public static Object unmarshal(Class className, String data)
    {
    	return unmarshal(className, new ByteArrayInputStream (data.getBytes ())) ;
    }    
    
    public static Object unmarshal(Class className, File file)
    {
    	Object result = null ;
		try
		{
			Document doc = parseXmlFile(new FileInputStream (file), false);
			if (doc != null)
			{
				result = createInstance(className, doc.getDocumentElement());
			}
		}
		catch (FileNotFoundException e)
		{
			e.printStackTrace();
		}
    	return result ;
    }

    public static void marshall(XMLSerializerUtil obj, OutputStream os)
    {
    	marshall (obj, new StreamResult(os)) ;
    }

    public static void marshall(XMLSerializerUtil obj, File file)
    {
    	marshall (obj, new StreamResult(file)) ;
    }

    private static void marshall(XMLSerializerUtil obj, Result result)
    {
    	Element elt = obj.element ;
        try
        {
            // Prepare the DOM document for writing
            Source source = new DOMSource(elt.getOwnerDocument());

            // Write the DOM document to the file
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.transform(source, result);
        }
        catch (TransformerConfigurationException e)
        {
        	e.printStackTrace();
        }
        catch (TransformerException e)
        {
        	e.printStackTrace();
        }
    }

    private static Object createInstance(Class clazz, Element elt)
	{
		try
		{
			Constructor constructor = clazz.getConstructor(new Class [] {Element.class});
			return constructor.newInstance(new Object [] {elt}) ; 
		}
		catch (SecurityException e)
		{
			e.printStackTrace();
		}
		catch (NoSuchMethodException e)
		{
			e.printStackTrace();
		}
		catch (InstantiationException e)
		{
			e.printStackTrace();
		}
		catch (IllegalAccessException e)
		{
			e.printStackTrace();
		}
		catch (InvocationTargetException e)
		{
			e.printStackTrace();
		}
		return null ;
	}
	
	public static Document createEmptyDocument (String rootElementName)
	{
		Document document;
		try
		{
			document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
			Element rootElement = document.createElement(rootElementName) ;
			document.appendChild(rootElement) ;
			return document ;
		}
		catch (ParserConfigurationException e)
		{
			e.printStackTrace();
		}
		catch (FactoryConfigurationError e)
		{
			e.printStackTrace();
		}
		return null ;
	}
}
